﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Copyright © Frank Nagl, 2009-2011
// admin@franknagl.de
//
//Some source code fragments from AForge.NET framework
// http://code.google.com/p/aforge/
//
namespace SBIP.Filter.NonSBIP
{
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using Helper;

    /// <summary>
    /// Flood fill algortihm with a list of <see cref="KeyPoints"/> as start 
    /// points.
    /// </summary>
    /// 
    /// <remarks><para>The filter starts its flood fill process at every 
    /// coordinate (see <see cref="PixelRGBA.Point">PixelRGBA.Point</see>) of the 
    /// <see cref="KeyPoints"/>. 
    /// <b>Note: The original color of a key point is stored in 
    /// <see cref="PixelRGBA.Color">PixelRGBA.Color</see>
    /// and the new color for flood filling is stored in 
    /// <see cref="PixelRGBA.Reserved">PixelRGBA.Reserved</see>.</b>
    /// The flood filling of one segment is stopped, when the neighbour region 
    /// has not a similar color.
    /// <see cref="Tolerance"/> determines the allowed deviation of the 
    /// neighbour's color for each color channel.
    /// </para>
    /// 
    /// <para>Sample usage:</para>
    /// <code>
    /// Bitmap bitmap = new Bitmap("Cathedral.jpg");
    ///
    /// NSCannyEdgeMarker marker = new NSCannyEdgeMarker();
    /// // draw canny edge map into image
    /// bitmap = marker.Apply(bitmap);
    ///
    /// NSKeyPointAndColorBasedSegmentation filter = 
    ///     new NSKeyPointAndColorBasedSegmentation();
    /// filter.Tolerance = Color.FromArgb(20, 20, 20);
    /// filter.KeyPoints = new List<list type="PixelRGBA"/>(4);
    ///
    /// int x = 480;
    /// int y = 430;
    /// Color c = bitmap.GetPixel(x, y);
    /// PixelRGBA keypoint1 = new PixelRGBA
    /// {
    ///     X = x,
    ///     Y = y,
    ///     R = c.R,
    ///     G = c.G,
    ///     B = c.B,
    ///     Reserved = Color.FromArgb(255, 0, 0)
    /// };
    ///
    /// x = 300;
    /// y = 300;
    /// c = bitmap.GetPixel(x, y);
    /// PixelRGBA keypoint2 = new PixelRGBA
    /// {
    ///     X = x,
    ///     Y = y,
    ///     R = c.R,
    ///     G = c.G,
    ///     B = c.B,
    ///     Reserved = Color.FromArgb(255, 255, 0)
    /// };
    ///
    /// x = 170;
    /// y = 240;
    /// c = bitmap.GetPixel(x, y);
    /// PixelRGBA keypoint3 = new PixelRGBA
    /// {
    ///     X = x,
    ///     Y = y,
    ///     R = c.R,
    ///     G = c.G,
    ///     B = c.B,
    ///     Reserved = Color.FromArgb(255, 0, 255)
    /// };
    ///
    /// x = 350;
    /// y = 450;
    /// c = bitmap.GetPixel(x, y);
    /// PixelRGBA keypoint4 = new PixelRGBA
    /// {
    ///     X = x,
    ///     Y = y,
    ///     R = c.R,
    ///     G = c.G,
    ///     B = c.B,
    ///     Reserved = Color.FromArgb(0, 0, 255)
    /// };
    ///
    /// filter.KeyPoints.Add(keypoint1);
    /// filter.KeyPoints.Add(keypoint2);
    /// filter.KeyPoints.Add(keypoint3);
    /// filter.KeyPoints.Add(keypoint4);
    /// // apply NSKeyPointAndColorBasedSegmentation filter
    /// bitmap = filter.Apply(bitmap);
    /// </code>
    /// 
    /// <para><b>Initial image:</b></para>
    /// <img src="../../Cathedral.jpg" />
    /// <para><b>Result image (with 4 key points):</b></para>
    /// <img src="../../NSKeyPointAndColorBasedSegmentation.png" />
    /// </remarks>
    public class NSKeyPointAndColorBasedSegmentation : BaseNonSBIPFilter
    {
        private class PixelRGBList
        {
            public List<PixelRGBA> InnerList { get; set; }
            /// <summary>Counts all pixels, belonging to this segment.</summary>
            public int Count { get; set; }
        }
        #region members
        // map of pixels, which are already checked by the flood fill algorithm
        private bool[,] checkedPixels;
        private int h;
        private List<PixelRGBA> newInnerList;
        private PixelRGBA p;
        private Rectangle rect;
        private int scan0Dst;      // pointer to first line of destination image
        private int scan0Src;      // pointer to first line of source image
        private int stride;     // size of image's line  
        private int w;

        #endregion members

        #region properties
        /// <summary>Allowed number of pixels per segment. Note: Last growing 
        /// routine is processed until finish, independently if this value is 
        /// overcalled.</summary>
        public int AllowedNumber { get; set; }

        /// <summary>Start points for segmentation process.</summary>
        public List<PixelRGBA> KeyPoints { get; set; }

        /// <summary> Segmentation tolerance. </summary>
        /// 
        /// <remarks><para>The tolerance value determines which neighbour pixel 
        /// of a segment pixel belongs to same segment. If the
        /// value is set to 0, then only color of the 
        /// <see cref="KeyPoints">key point</see> is filled. If the value is 
        /// not 0, then other colors may be filled as well, which are similar to
        ///  the color of the key point within the specified tolerance.</para>
        /// 
        /// <para>The tolerance value is specified as <see cref="System.Drawing.Color"/>,
        /// where each component (R, G and B) represents tolerance for the 
        /// corresponding component of color. This allows to set different 
        /// tolerances for red, green and blue components.</para>
        /// </remarks>
        /// 
        public Color Tolerance { get; set; }

        #endregion properties

        /// <summary>
        /// Initializes a new instance of the 
        /// <see cref="NSKeyPointAndColorBasedSegmentation"/> class.
        /// </summary>
        /// 
        public NSKeyPointAndColorBasedSegmentation()
        {
            SupportedSrcPixelFormats = PixelFormatFlags.Format24BppRgb;
            AllowedNumber = int.MaxValue;
        }

        /// <summary>
        /// Processes the filter on the passed <paramref name="srcData"/>
        /// resulting into <paramref name="dstData"/>.
        /// </summary>
        /// <param name="srcData">The source bitmap data.</param>
        /// <param name="dstData">The destination bitmap data.</param>
        protected override void Process(BitmapData srcData, BitmapData dstData)
        {
            h = srcData.Height;
            w = srcData.Width;
            rect = new Rectangle(1, 1, w - 1, h - 1);
            scan0Dst = dstData.Scan0.ToInt32();
            scan0Src = srcData.Scan0.ToInt32();
            stride = srcData.Stride;
            // create map visited pixels
            checkedPixels = new bool[w, h];
            bool inProcess = true;
            List<PixelRGBList> outerList =
                new List<PixelRGBList>(KeyPoints.Count);

            // start list
            for (int i = 0; i < KeyPoints.Count; i++)
            {
                // remove key point, if it is not in the image
                if (!rect.Contains(KeyPoints[i].X, KeyPoints[i].Y))
                {
                    KeyPoints.RemoveAt(i);
                    i--;
                    continue;
                }

                PixelRGBList tmpList = new PixelRGBList();
                tmpList.InnerList = new List<PixelRGBA>(1/*w * h*/);
                tmpList.InnerList.Add(KeyPoints[i]);
                tmpList.Count++;
                outerList.Add(tmpList);
                SetDstColor(KeyPoints[i].X, KeyPoints[i].Y, KeyPoints[i].Reserved);
                checkedPixels[KeyPoints[i].X, KeyPoints[i].Y] = true;
            }

            while (inProcess)
            {
                inProcess = false;
                for (int i = 0; i < outerList.Count; i++)
                {
                    newInnerList = new List<PixelRGBA>(outerList[i].InnerList.Count * 8);
                    foreach (PixelRGBA t in outerList[i].InnerList)
                    {
                        p = t;
                        // check all 8 direct neighbours and add to newList, if necessary
                        CheckNeighbour(new Size(-1, -1));
                        CheckNeighbour(new Size(0, -1));
                        CheckNeighbour(new Size(1, -1));
                        CheckNeighbour(new Size(-1, 0));
                        CheckNeighbour(new Size(1, 0));
                        CheckNeighbour(new Size(-1, 1));
                        CheckNeighbour(new Size(0, 1));
                        CheckNeighbour(new Size(1, 1));
                    }

                    outerList[i].InnerList.Clear();
                    if (newInnerList.Count == 0 || outerList[i].Count >= AllowedNumber)
                    {
                        outerList.RemoveAt(i);
                        i--;
                    }
                    else
                    {
                        outerList[i].InnerList.AddRange(newInnerList);
                        outerList[i].Count++;
                        inProcess = true;
                    }
                }
            }
        }

        private void CheckNeighbour(Size size)
        {
            PixelRGBA p1 = p + size;
            if (rect.Contains(p1.Point) &&
                !checkedPixels[p1.X, p1.Y])
            {
                Color c = GetSrcColor(p1.X, p1.Y);
                p1.R = c.R;
                p1.G = c.G;
                p1.B = c.B;
                if (p.IsSameRGBColor(p1, Tolerance))
                //if (p.IsSameColorTriple(p1, directTolerance, ColorSpace))
                {
                    SetDstColor(p1.X, p1.Y, p1.Reserved);
                    newInnerList.Add(p1);
                    checkedPixels[p1.X, p1.Y] = true;
                }
            }
        }

        // Convert image coordinate to pointer for destination image
        private int CoordsToPointerDst(int x, int y)
        {
            return scan0Dst + (stride * y) + x * 3;
        }

        // Convert image coordinate to pointer for source image
        private int CoordsToPointerSrc(int x, int y)
        {
            return scan0Src + (stride * y) + x * 3;
        }

        // Get color from passed image coordinate
        private unsafe Color GetSrcColor(int x, int y)
        {
            byte* ptr = (byte*)CoordsToPointerSrc(x, y);
            return Color.FromArgb(255, ptr[RGBA.R], ptr[RGBA.G], ptr[RGBA.B]);
        }

        // Get color from passed image coordinate
        private unsafe void SetDstColor(int x, int y, Color c)
        {
            byte* ptr = (byte*)CoordsToPointerDst(x, y);
            ptr[RGBA.R] = c.R;
            ptr[RGBA.G] = c.G;
            ptr[RGBA.B] = c.B;
        }
    }
}
